"""
Fortran module analysis for CodeMCP framework.

This module provides analysis of Fortran module system including:
- Module definition analysis
- USE statement processing
- PUBLIC/PRIVATE visibility analysis  
- Module dependency tracking
- Submodule relationships
"""

import logging
from typing import Dict, List, Any, Set, Optional, Tuple
from pathlib import Path
from collections import defaultdict

from ...core.error_handler import AnalysisError, log_info


class ModuleInfo:
    """Information about a Fortran module."""
    
    def __init__(self, name: str, file_path: str = "", line_number: int = 0):
        self.name = name
        self.file_path = file_path
        self.line_number = line_number
        self.procedures = {}  # name -> procedure info
        self.types = {}  # name -> type info
        self.variables = {}  # name -> variable info
        self.interfaces = {}  # name -> interface info
        self.submodules = set()  # Set of submodule names
        self.parent_module = None  # For submodules
        self.uses = []  # List of USE statements
        self.public_entities = set()  # Explicitly public entities
        self.private_entities = set()  # Explicitly private entities
        self.default_private = False  # PRIVATE statement without entity list
        self.contains_section = False  # Has CONTAINS section
        self.is_submodule = False
    
    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary representation."""
        return {
            'name': self.name,
            'file_path': self.file_path,
            'line_number': self.line_number,
            'procedures': {name: proc for name, proc in self.procedures.items()},
            'types': {name: typ for name, typ in self.types.items()},
            'variables': {name: var for name, var in self.variables.items()},
            'interfaces': {name: iface for name, iface in self.interfaces.items()},
            'submodules': list(self.submodules),
            'parent_module': self.parent_module,
            'uses': self.uses,
            'public_entities': list(self.public_entities),
            'private_entities': list(self.private_entities),
            'default_private': self.default_private,
            'contains_section': self.contains_section,
            'is_submodule': self.is_submodule
        }
    
    def is_public(self, entity_name: str) -> bool:
        """Check if an entity is public."""
        if entity_name in self.private_entities:
            return False
        if entity_name in self.public_entities:
            return True
        # If default is private, then it's private unless explicitly public
        return not self.default_private


class UseStatement:
    """Information about a USE statement."""
    
    def __init__(self, module_name: str, line_number: int = 0):
        self.module_name = module_name
        self.line_number = line_number
        self.only = False  # True if USE ... ONLY
        self.intrinsic = False  # True if USE, INTRINSIC
        self.non_intrinsic = False  # True if USE, NON_INTRINSIC
        self.only_list = []  # List of entities for ONLY clause
        self.rename_list = []  # List of (local_name, use_name) pairs
    
    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary representation."""
        return {
            'module_name': self.module_name,
            'line_number': self.line_number,
            'only': self.only,
            'intrinsic': self.intrinsic,
            'non_intrinsic': self.non_intrinsic,
            'only_list': self.only_list,
            'rename_list': self.rename_list
        }


class ModuleAnalyzer:
    """
    Fortran module system analyzer.
    
    Analyzes Fortran modules, submodules, and USE statements to understand:
    - Module structure and contents
    - Visibility (PUBLIC/PRIVATE) rules
    - Module dependencies through USE statements
    - Submodule hierarchies
    - Name conflicts and resolution
    """
    
    def __init__(self):
        self.logger = logging.getLogger(__name__)
        self.modules = {}  # module_name -> ModuleInfo
        self.use_graph = defaultdict(set)  # module -> set of used modules
        self.reverse_use_graph = defaultdict(set)  # module -> set of modules that use it
        self.intrinsic_modules = {
            'iso_c_binding', 'iso_fortran_env', 'ieee_exceptions',
            'ieee_arithmetic', 'ieee_features', 'omp_lib', 'omp_lib_kinds'
        }
    
    def analyze_modules(self, parsed_data: Dict[str, Any]) -> Dict[str, Any]:
        """
        Analyze modules in parsed Fortran data.
        
        Args:
            parsed_data: Parsed Fortran file data
            
        Returns:
            Module analysis results
        """
        try:
            filename = parsed_data.get('filename', 'unknown')
            log_info(f"Analyzing modules in {filename}")
            
            # Process modules
            for module_data in parsed_data.get('modules', []):
                module_info = self._analyze_module(module_data, filename)
                self.modules[module_info.name] = module_info
            
            # Process USE statements
            for use_data in parsed_data.get('uses', []):
                self._process_use_statement(use_data, filename)
            
            # Build dependency graphs
            self._build_dependency_graphs()
            
            # Detect circular dependencies
            cycles = self._detect_circular_dependencies()
            
            result = {
                'filename': filename,
                'modules': {name: mod.to_dict() for name, mod in self.modules.items()},
                'use_graph': {mod: list(uses) for mod, uses in self.use_graph.items()},
                'dependency_cycles': cycles,
                'intrinsic_modules_used': self._get_intrinsic_modules_used(),
                'statistics': self._compute_statistics()
            }
            
            log_info(f"Module analysis complete for {filename}: "
                    f"{len(self.modules)} modules, "
                    f"{sum(len(uses) for uses in self.use_graph.values())} use relationships")
            
            return result
            
        except Exception as e:
            self.logger.error(f"Error analyzing modules: {e}")
            raise AnalysisError(f"Module analysis failed: {e}")
    
    def _analyze_module(self, module_data: Dict[str, Any], filename: str) -> ModuleInfo:
        """Analyze a single module."""
        module_info = ModuleInfo(
            name=module_data['name'],
            file_path=filename,
            line_number=module_data.get('line_number', 0)
        )
        
        # Process procedures within module
        for proc_name in module_data.get('procedures', []):
            module_info.procedures[proc_name] = {
                'name': proc_name,
                'type': 'unknown',  # Would need more detailed parsing
                'public': True  # Default assumption
            }
        
        # Process types within module
        for type_name in module_data.get('types', []):
            module_info.types[type_name] = {
                'name': type_name,
                'public': True  # Default assumption
            }
        
        # Process USE statements within module
        for use_name in module_data.get('uses', []):
            use_stmt = UseStatement(use_name)
            module_info.uses.append(use_stmt.to_dict())
        
        return module_info
    
    def _process_use_statement(self, use_data: Dict[str, Any], filename: str):
        """Process a USE statement."""
        module_name = use_data.get('module', 'unknown')
        
        # Create USE statement info
        use_stmt = UseStatement(
            module_name=module_name,
            line_number=use_data.get('line_number', 0)
        )
        use_stmt.only = use_data.get('only', False)
        use_stmt.intrinsic = use_data.get('intrinsic', False)
        use_stmt.non_intrinsic = use_data.get('non_intrinsic', False)
        use_stmt.only_list = use_data.get('only_list', [])
        use_stmt.rename_list = use_data.get('rename_list', [])
        
        # Add to dependency graph
        # For now, we track at file level since we don't have the using module context
        if filename not in self.use_graph:
            self.use_graph[filename] = set()
        self.use_graph[filename].add(module_name)
        self.reverse_use_graph[module_name].add(filename)
    
    def _build_dependency_graphs(self):
        """Build module dependency graphs."""
        # Build module-to-module dependencies
        for module_name, module_info in self.modules.items():
            for use_data in module_info.uses:
                used_module = use_data['module_name']
                self.use_graph[module_name].add(used_module)
                self.reverse_use_graph[used_module].add(module_name)
    
    def _detect_circular_dependencies(self) -> List[List[str]]:
        """Detect circular dependencies in module usage."""
        cycles = []
        visited = set()
        rec_stack = set()
        path = []
        
        def dfs(module: str) -> bool:
            if module in rec_stack:
                # Found a cycle
                cycle_start = path.index(module)
                cycle = path[cycle_start:] + [module]
                cycles.append(cycle)
                return True
            
            if module in visited:
                return False
            
            visited.add(module)
            rec_stack.add(module)
            path.append(module)
            
            for used_module in self.use_graph.get(module, []):
                if used_module not in self.intrinsic_modules:  # Skip intrinsic modules
                    if dfs(used_module):
                        return True
            
            rec_stack.remove(module)
            path.pop()
            return False
        
        for module in self.modules:
            if module not in visited:
                dfs(module)
        
        return cycles
    
    def _get_intrinsic_modules_used(self) -> Set[str]:
        """Get set of intrinsic modules used."""
        intrinsic_used = set()
        
        for uses in self.use_graph.values():
            for module in uses:
                if module.lower() in self.intrinsic_modules:
                    intrinsic_used.add(module)
        
        return intrinsic_used
    
    def _compute_statistics(self) -> Dict[str, Any]:
        """Compute module analysis statistics."""
        total_modules = len(self.modules)
        total_uses = sum(len(uses) for uses in self.use_graph.values())
        
        # Count different types of entities
        total_procedures = sum(len(mod.procedures) for mod in self.modules.values())
        total_types = sum(len(mod.types) for mod in self.modules.values())
        
        # Count modules with different characteristics
        modules_with_contains = sum(1 for mod in self.modules.values() if mod.contains_section)
        modules_with_private = sum(1 for mod in self.modules.values() if mod.default_private)
        
        return {
            'total_modules': total_modules,
            'total_use_statements': total_uses,
            'total_procedures': total_procedures,
            'total_types': total_types,
            'modules_with_contains': modules_with_contains,
            'modules_with_default_private': modules_with_private,
            'intrinsic_modules_used': len(self._get_intrinsic_modules_used())
        }
    
    def get_module_dependencies(self, module_name: str) -> Dict[str, Any]:
        """
        Get dependencies for a specific module.
        
        Args:
            module_name: Name of the module
            
        Returns:
            Dictionary with dependency information
        """
        if module_name not in self.modules:
            return {}
        
        # Direct dependencies
        direct_deps = set(self.use_graph.get(module_name, []))
        
        # Transitive dependencies
        transitive_deps = set()
        visited = set()
        
        def collect_deps(mod: str):
            if mod in visited or mod in self.intrinsic_modules:
                return
            visited.add(mod)
            
            for dep in self.use_graph.get(mod, []):
                if dep not in self.intrinsic_modules:
                    transitive_deps.add(dep)
                    collect_deps(dep)
        
        for dep in direct_deps:
            collect_deps(dep)
        
        # Modules that depend on this module
        dependents = set(self.reverse_use_graph.get(module_name, []))
        
        return {
            'direct_dependencies': list(direct_deps),
            'transitive_dependencies': list(transitive_deps),
            'dependents': list(dependents),
            'dependency_depth': self._compute_dependency_depth(module_name),
            'is_leaf_module': len(direct_deps) == 0
        }
    
    def _compute_dependency_depth(self, module_name: str) -> int:
        """Compute the maximum dependency depth for a module."""
        visited = set()
        
        def max_depth(mod: str) -> int:
            if mod in visited or mod in self.intrinsic_modules:
                return 0
            visited.add(mod)
            
            max_d = 0
            for dep in self.use_graph.get(mod, []):
                if dep not in self.intrinsic_modules:
                    max_d = max(max_d, max_depth(dep) + 1)
            
            visited.remove(mod)
            return max_d
        
        return max_depth(module_name)
    
    def find_name_conflicts(self) -> Dict[str, List[str]]:
        """
        Find potential name conflicts between modules.
        
        Returns:
            Dictionary mapping entity names to list of modules that define them
        """
        entity_modules = defaultdict(list)
        
        for module_name, module_info in self.modules.items():
            # Check procedures
            for proc_name in module_info.procedures:
                if module_info.is_public(proc_name):
                    entity_modules[proc_name].append(f"{module_name}:procedure")
            
            # Check types
            for type_name in module_info.types:
                if module_info.is_public(type_name):
                    entity_modules[type_name].append(f"{module_name}:type")
            
            # Check variables
            for var_name in module_info.variables:
                if module_info.is_public(var_name):
                    entity_modules[var_name].append(f"{module_name}:variable")
        
        # Return only entities with conflicts
        conflicts = {name: modules for name, modules in entity_modules.items() 
                    if len(modules) > 1}
        
        return conflicts
    
    def get_use_tree(self, root_module: str) -> Dict[str, Any]:
        """
        Build a tree showing module use relationships.
        
        Args:
            root_module: Starting module
            
        Returns:
            Tree representation of USE relationships
        """
        if root_module not in self.modules:
            return {}
        
        visited = set()
        
        def build_tree(module: str, depth: int = 0) -> Dict[str, Any]:
            if module in visited or module in self.intrinsic_modules:
                return {'name': module, 'is_cycle': module in visited, 'children': []}
            
            visited.add(module)
            
            tree_node = {
                'name': module,
                'depth': depth,
                'is_intrinsic': module.lower() in self.intrinsic_modules,
                'children': []
            }
            
            for used_module in sorted(self.use_graph.get(module, [])):
                child = build_tree(used_module, depth + 1)
                tree_node['children'].append(child)
            
            visited.remove(module)
            return tree_node
        
        return build_tree(root_module)